/*
   FieldVis - A Tool for Visualizing Astrophysical Magnetohydrodynamic Flow
   
   Copyright (C) 2006 - 2008     FieldVis Development Team
  
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
  
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   File        : camera.cpp
   Author      : Matt Heinzen  (heinzen@cs.umn.edu)
                 Digital Technology Center - University of Minnesota
                 
   Description : source file for flowviewer camera navigation class
*/

#include "camera.h"


void Camera::GetState(Vector3D &PosCopy, Vector3D &LookCopy, Vector3D &UpCopy,
                      Vector3D &SideCopy)
{
   PosCopy = Pos;
   LookCopy = Look;
   UpCopy = Up;
   SideCopy = Side;
}

void Camera::SetState(Vector3D const NewPos, Vector3D const NewLook, Vector3D const NewUp,
                      Vector3D const NewSide)
{
   // assumes NewLook, NewUp and NewSide are normalized for proper function

   Pos = NewPos;
   Look = NewLook;
   Up = NewUp;
   Side = NewSide;
}

void Camera::PushState()
{
   PushedPos = Pos;
   PushedLook = Look;
   PushedUp = Up;
   PushedSide = Side;
}

void Camera::PopState()
{
   Pos = PushedPos;
   Look = PushedLook;
   Up = PushedUp;
   Side = PushedSide;
}

void Camera::Zoom(double const Dist)
{
   // translates camera forwards or backwards along Look vector by given distance

   Pos = Pos + (Look * Dist);
}

void Camera::Pan(double const dX, double const dY)
{
   // translates camera horizontally and vertically along Side and Up vectors
   //by given distances

   Pos = Pos + (Side * dX) + (Up * dY);
}

void Camera::Roll(double const Angle)
{
   // rotates the camera around the Look vector by the given angle

   Matrix4x4 M;

   // calculate the rotation matrix
   M = CalcRotMat(Look, Angle);

   // transform the camera vectors
   Look = TransformVector(Look, M);
   Up = TransformVector(Up, M);
   Side = Look.Cross(Up);
}

void Camera::Orbit(Vector3D const &Pivot, int const dX, int const dY,
                   double const PixelsPer180)
{
   // rotates camera around the given pivot point to simulate spinning all
   //   objects in the view
   // PixelsPer180 is the distance to move to achieve a 180 degree rotation

   Vector3D Axis, PivotV, RotPivotV;
   double Angle;
   Matrix4x4 M;

   // return if no motion
   if (dX == 0 && dY == 0) return;

   // calculate axis of rotation
   Axis.SetX(-dX * Up.GetX() + dY * Side.GetX());
   Axis.SetY(-dX * Up.GetY() + dY * Side.GetY());
   Axis.SetZ(-dX * Up.GetZ() + dY * Side.GetZ());
   Axis.Normalize();

   // calculate rotation angle from magnitude of displacement
   Angle = sqrt(dX * dX + dY * dY) / PixelsPer180 * M_PI;

   // calculate the vector from the orbit center to the camera
   PivotV = Pos - Pivot;

   // calculate the rotation matrix
   M = CalcRotMat(Axis, Angle);

   // calculate the rotated vector
   RotPivotV = TransformVector(PivotV, M);

   // subtract the original vector from the camera position and add the
   // rotated vector
   Pos = (Pos - PivotV) + RotPivotV;

   // transform the camera vectors
   Look = TransformVector(Look, M);
   Up = TransformVector(Up, M);
   Side = Look.Cross(Up);
}

void Camera::RollOrbit(Vector3D &Pivot, double const Angle)
{
   // rolls the camera by the given angle around an axis through the camera
   //   position and the given pivot point to simulate rolling around all
   //   objects in the view

   Vector3D Axis, PivotV, RotPivotV;
   Matrix4x4 M;

   /*Axis = Look;*/
   Axis = Pivot - Pos;
   Axis.Normalize();

   // calculate the vector from the orbit center to the camera
   PivotV = Pos - Pivot;

   // calculate the rotation matrix
   M = CalcRotMat(Axis, Angle);

   // calculate the rotated vector
   RotPivotV = TransformVector(PivotV, M);

   // subtract the original vector from the camera position and add the
   // rotated vector
   Pos = (Pos - PivotV) + RotPivotV;

   // transform the camera vectors
   Look = TransformVector(Look, M);
   Up = TransformVector(Up, M);
   Side = Look.Cross(Up);
}


void Camera::GoHome(Vector3D origin)
{
   Look = Vector3D(VIEW_START_LOOK);
   Up = Vector3D(VIEW_START_UP);

   Look.Normalize();
   Up.Normalize();

   // make sure camera vectors are absolutely perpendicular
   Side = Look.Cross(Up);
   Up = Side.Cross(Look);

   // set initial position   
   Pos = origin - (Look * VIEW_START_DIST_FROM_ORIGIN);
}

